package reesoft.idgenerator.service;

import reesoft.idgenerator.IdGenerateException;
import reesoft.idgenerator.OnlyPartialIdAssignedException;
import reesoft.idgenerator.entity.IdRange;
import reesoft.idgenerator.service.idstore.IdStore;

/**
 * 序号生成类
 *
 * Created by reesoft on 2017-08-28
 */
public class IdGenerator
{
    /**
     * 序号的名称
     */
    private String name;

    /**
     * 存储未分配的序号信息的对象
     */
    private IdStore idStore;

    /**
     * 每个批次包含的序号数量
     */
    private long batchAmount;

    /**
     * 当前可用序号批次的最大序号 + 1
     */
    private long batchMaxValue;

    /**
     * 下个可用序号
     */
    private long nextValue;

    /**
     * 构造函数
     *
     * @param name        序号的名称
     * @param idStore     存储未分配的序号信息的对象
     * @param batchAmount 每个批次包含的序号数量
     * @throws IdGenerateException
     */
    public IdGenerator(String name, IdStore idStore, long batchAmount) throws IdGenerateException
    {
        this.name = name;
        this.idStore = idStore;
        this.batchAmount = batchAmount;
        this.nextValue = idStore.loadAndIncrease(name, batchAmount);
        this.batchMaxValue = this.nextValue + batchAmount;
    }

    /**
     * 用缺省的初始值和最大值初始化序号存储空间
     *
     * @param name 序号的名称
     * @throws IdGenerateException 异常必须得到处理，否则后续分配序号操作将不能成功
     */
    void init(String name) throws IdGenerateException
    {
        idStore.init(name);
    }

    /**
     * 初始化序号存储空间
     * 如果序号不存在，初始化为 initValue；序号已存在则不做任何操作
     *
     * @param name      序号的名称
     * @param initValue 初始值
     * @param maxValue  最大值
     * @throws IdGenerateException 异常必须得到处理，否则后续分配序号操作将不能成功
     */
    void init(String name, long initValue, long maxValue) throws IdGenerateException
    {
        idStore.init(name, initValue, maxValue);
    }

    /**
     * 获取下个序号
     *
     * @return
     * @throws IdGenerateException
     */
    public long next() throws IdGenerateException
    {
        return next(1).getStartValue();
    }

    /**
     * 获取下一批序号
     *
     * @param amount 数量
     * @return 返回序号批次中第一个序号
     * @throws IdGenerateException
     */
    public synchronized IdRange next(long amount) throws IdGenerateException
    {
        if (nextValue >= batchMaxValue)
        {
            try
            {
                nextValue = idStore.loadAndIncrease(name, batchAmount);
                batchMaxValue = nextValue + batchAmount - 1;
            }
            catch (OnlyPartialIdAssignedException e)
            {
                nextValue = e.getIdRange().getStartValue();
                batchMaxValue = nextValue + e.getIdRange().getAmount() - 1;
            }
        }

        IdRange idRange = new IdRange(nextValue, 0);

        if (nextValue + amount > batchMaxValue)
        {
            // 只能分配部分
            idRange.setAmount(batchMaxValue - nextValue + 1);

            nextValue = batchMaxValue + 1;
        }
        else
        {
            idRange.setAmount(amount);

            nextValue += amount;
        }

        return idRange;
    }
}
